package com.example.socket.handler;

import com.example.socket.core.Message;
import com.example.socket.core.Session;
import com.example.socket.exception.TypeDefinitionNotFound;
import com.example.socket.filter.session.NettySessionManager;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.SocketAddress;

/**
 * 处理器
 */
@Sharable
public class NettyHandler extends ChannelDuplexHandler {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /** 消息分发器 */
    protected Dispatcher dispatcher;

    /** 构造方法 */
    public NettyHandler(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        Session session = NettySessionManager.lookup(channel);
        Message message = (Message) msg;
        dispatcher.receive(message, session);
        super.channelRead(ctx, msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception {
        SocketAddress remoteAddress = ctx.channel().remoteAddress();
        // write to closed socket channel
        if (throwable instanceof ChannelException || throwable instanceof DecoderException
                || throwable instanceof TypeDefinitionNotFound) {
            logger.error("{}\tError: {}, - {}", new Object[]{throwable.getClass().getName(), throwable.getMessage(),
                    remoteAddress});
            return;
        }
        // 生成错误堆栈信息
        StringBuilder sb = new StringBuilder();
        Throwable ex = throwable;
        while (ex != null) {
            StackTraceElement[] stackTrace = ex.getStackTrace();
            for (StackTraceElement st : stackTrace) {
                if (throwable instanceof IOException && st.getClassName().equals("sun.nio.ch.SocketChannelImpl")) {
                    // connection reset by peer ...
                    // logger.error("{}\tError: {}", throwable.getClass().getName(), throwable.getMessage());
                    return;
                }
                sb.append("\t").append(st.toString()).append("\n");
            }
            if (ex != ex.getCause()) {
                ex = ex.getCause();
                if (ex != null) {
                    sb.append("CAUSE\n").append(ex.getMessage()).append(ex).append("\n");
                    ;
                }

            } else {
                break;
            }
        }

        logger.error("{}\tError: {} - {}\n{}", new Object[]{throwable.getClass().getName(), throwable.getMessage(),
                remoteAddress, sb.toString()});
        super.exceptionCaught(ctx, throwable);
    }

}
